/* w_graph.c
 *
 * Micropolis, Unix Version.  This game was released for the Unix platform
 * in or about 1990 and has been modified for inclusion in the One Laptop
 * Per Child program.  Copyright (C) 1989 - 2007 Electronic Arts Inc.  If
 * you need assistance with this program, you may contact:
 *   http://wiki.laptop.org/go/Micropolis  or email  micropolis@laptop.org.
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.  You should have received a
 * copy of the GNU General Public License along with this program.  If
 * not, see <http://www.gnu.org/licenses/>.
 * 
 *             ADDITIONAL TERMS per GNU GPL Section 7
 * 
 * No trademark or publicity rights are granted.  This license does NOT
 * give you any right, title or interest in the trademark SimCity or any
 * other Electronic Arts trademark.  You may not distribute any
 * modification of this program using the trademark SimCity or claim any
 * affliation or association with Electronic Arts Inc. or its employees.
 * 
 * Any propagation or conveyance of this program must include this
 * copyright notice and these terms.
 * 
 * If you convey this program (or any modifications of it) and assume
 * contractual liability for the program to recipients of it, you agree
 * to indemnify Electronic Arts for any liability that those contractual
 * assumptions impose on Electronic Arts.
 * 
 * You may not misrepresent the origins of this program; modified
 * versions of the program must be marked as such and not identified as
 * the original program.
 * 
 * This disclaimer supplements the one included in the General Public
 * License.  TO THE FULLEST EXTENT PERMISSIBLE UNDER APPLICABLE LAW, THIS
 * PROGRAM IS PROVIDED TO YOU "AS IS," WITH ALL FAULTS, WITHOUT WARRANTY
 * OF ANY KIND, AND YOUR USE IS AT YOUR SOLE RISK.  THE ENTIRE RISK OF
 * SATISFACTORY QUALITY AND PERFORMANCE RESIDES WITH YOU.  ELECTRONIC ARTS
 * DISCLAIMS ANY AND ALL EXPRESS, IMPLIED OR STATUTORY WARRANTIES,
 * INCLUDING IMPLIED WARRANTIES OF MERCHANTABILITY, SATISFACTORY QUALITY,
 * FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS, AND WARRANTIES (IF ANY) ARISING FROM A COURSE OF DEALING,
 * USAGE, OR TRADE PRACTICE.  ELECTRONIC ARTS DOES NOT WARRANT AGAINST
 * INTERFERENCE WITH YOUR ENJOYMENT OF THE PROGRAM; THAT THE PROGRAM WILL
 * MEET YOUR REQUIREMENTS; THAT OPERATION OF THE PROGRAM WILL BE
 * UNINTERRUPTED OR ERROR-FREE, OR THAT THE PROGRAM WILL BE COMPATIBLE
 * WITH THIRD PARTY SOFTWARE OR THAT ANY ERRORS IN THE PROGRAM WILL BE
 * CORRECTED.  NO ORAL OR WRITTEN ADVICE PROVIDED BY ELECTRONIC ARTS OR
 * ANY AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY.  SOME
 * JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF OR LIMITATIONS ON IMPLIED
 * WARRANTIES OR THE LIMITATIONS ON THE APPLICABLE STATUTORY RIGHTS OF A
 * CONSUMER, SO SOME OR ALL OF THE ABOVE EXCLUSIONS AND LIMITATIONS MAY
 * NOT APPLY TO YOU.
 */
#include "sim.h"

short NewGraph = 0;
short AllMax;
unsigned char *History10[HISTORIES];
unsigned char *History120[HISTORIES];
int HistoryInitialized = 0;
short Graph10Max, Graph120Max;
Tcl_HashTable GraphCmds;
int GraphUpdateTime = 100;

void DoUpdateGraph(SimGraph * graph);
void DoNewGraph(SimGraph * graph);
void DoResizeGraph(SimGraph * graph, int w, int h);
void InitNewGraph(SimGraph * graph);

#define DEF_GRAPH_FONT	"-Adobe-Helvetica-Bold-R-Normal-*-140-*"
#define DEF_GRAPH_BG_COLOR	"#b0b0b0"
#define DEF_GRAPH_BG_MONO	"#ffffff"
#define DEF_GRAPH_BORDER_WIDTH	"0"
#define DEF_GRAPH_RELIEF	"flat"

Tk_ConfigSpec GraphConfigSpecs[] = {
    {TK_CONFIG_FONT, "-font", (char *) NULL, (char *) NULL,
     DEF_GRAPH_FONT, Tk_Offset(SimGraph, fontPtr), 0},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
     DEF_GRAPH_BG_COLOR, Tk_Offset(SimGraph, border),
     TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "background", "Background",
     DEF_GRAPH_BG_MONO, Tk_Offset(SimGraph, border),
     TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
     DEF_GRAPH_BORDER_WIDTH, Tk_Offset(SimGraph, borderWidth), 0},
    {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
     DEF_GRAPH_RELIEF, Tk_Offset(SimGraph, relief), 0},
    {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
     (char *) NULL, 0, 0}
};

XDisplay *FindXDisplay();

static void DisplaySimGraph(ClientData clientData)
{
    SimGraph *graph = (SimGraph *) clientData;
    Tk_Window tkwin = graph->tkwin;

    graph->flags &= ~VIEW_REDRAW_PENDING;

//fprintf(stderr, "DisplaySimGraph token %d\n", graph->draw_graph_token);

    assert(graph->draw_graph_token != 0);

    if(graph->draw_graph_token != 0)
    {
//    Tk_DeleteTimerHandler(graph->draw_graph_token);
        graph->draw_graph_token = 0;
    }

    if(graph->visible && (tkwin != NULL) && Tk_IsMapped(tkwin))
    {
        DoUpdateGraph(graph);
    }
}

void DestroySimGraph(ClientData clientData)
{
    SimGraph *graph = (SimGraph *) clientData;

    DestroyGraph(graph);
}

void EventuallyRedrawGraph(SimGraph * graph)
{
    if(!(graph->flags & VIEW_REDRAW_PENDING))
    {
        assert(graph->draw_graph_token == 0);
        if(graph->draw_graph_token == 0)
        {
            graph->draw_graph_token = Tk_CreateTimerHandler(GraphUpdateTime, DisplaySimGraph, (ClientData) graph);
            graph->flags |= VIEW_REDRAW_PENDING;
//fprintf(stderr, "EventuallyRedrawGraph token %d\n", graph->draw_graph_token);
        }
    }
}

void SimGraphEventProc(ClientData clientData, XEvent * eventPtr)
{
    SimGraph *graph = (SimGraph *) clientData;

    if((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0))
    {
        graph->visible = 1;
        EventuallyRedrawGraph(graph);
    }
    else if(eventPtr->type == MapNotify)
    {
        graph->visible = 1;
    }
    else if(eventPtr->type == UnmapNotify)
    {
        graph->visible = 0;
    }
    else if(eventPtr->type == VisibilityNotify)
    {
        if(eventPtr->xvisibility.state == VisibilityFullyObscured)
            graph->visible = 0;
        else
            graph->visible = 1;
    }
    else if(eventPtr->type == ConfigureNotify)
    {
        DoResizeGraph(graph, eventPtr->xconfigure.width, eventPtr->xconfigure.height);
        EventuallyRedrawGraph(graph);
    }
    else if(eventPtr->type == DestroyNotify)
    {
        Tcl_DeleteCommand(graph->interp, Tk_PathName(graph->tkwin));
        graph->tkwin = NULL;
        if(graph->flags & VIEW_REDRAW_PENDING)
        {
//fprintf(stderr, "SimGraphEventProc Destroy token %d\n", graph->draw_graph_token);
            assert(graph->draw_graph_token != 0);
            if(graph->draw_graph_token != 0)
            {
                Tk_DeleteTimerHandler(graph->draw_graph_token);
                graph->draw_graph_token = 0;
            }
            graph->flags &= ~VIEW_REDRAW_PENDING;
        }
        Tk_EventuallyFree((ClientData) graph, DestroySimGraph);
    }
}

int GraphCmdconfigure(GRAPH_ARGS)
{
    if(argc == 2)
    {
        Tk_ConfigureInfo(interp, graph->tkwin, GraphConfigSpecs, (char *) graph, (char *) NULL, 0);
    }
    else if(argc == 3)
    {
        Tk_ConfigureInfo(interp, graph->tkwin, GraphConfigSpecs, (char *) graph, argv[2], 0);
    }
    else
    {
        ConfigureSimGraph(interp, graph, argc - 2, argv + 2, TK_CONFIG_ARGV_ONLY);
    }
    return TCL_OK;
}

int GraphCmdposition(GRAPH_ARGS)
{
    if((argc != 2) && (argc != 4))
    {
        return TCL_ERROR;
    }
    if(argc == 4)
    {
        if((Tcl_GetInt(interp, argv[2], &graph->w_x) != TCL_OK) || (Tcl_GetInt(interp, argv[3], &graph->w_y) != TCL_OK))
        {
            return TCL_ERROR;
        }
    }
    sprintf(interp->result, "%d %d", graph->w_x, graph->w_y);
    return TCL_OK;
}

int GraphCmdsize(GRAPH_ARGS)
{
    if((argc != 2) && (argc != 4))
    {
        return TCL_ERROR;
    }
    if(argc == 4)
    {
        int w, h;

        if(Tcl_GetInt(interp, argv[2], &w) != TCL_OK)
        {
            return TCL_ERROR;
        }
        if(Tcl_GetInt(interp, argv[3], &h) != TCL_OK)
        {
            return TCL_ERROR;
        }
        graph->w_width = w;
        graph->w_height = h;
    }
    sprintf(interp->result, "%d %d", graph->w_width, graph->w_height);
    return TCL_OK;
}

int GraphCmdVisible(GRAPH_ARGS)
{
    int visible;

    if((argc != 2) && (argc != 3))
    {
        Tcl_AppendResult(interp, "wrong # args", (char *) NULL);
        return TCL_ERROR;
    }

    if(argc == 3)
    {
        if((Tcl_GetInt(interp, argv[2], &visible) != TCL_OK) || (visible < 0) || (visible > 1))
        {
            Tcl_AppendResult(interp, " bogus args", (char *) NULL);
            return TCL_ERROR;
        }

        graph->visible = visible;
    }

    sprintf(interp->result, "%d", graph->visible);

    return TCL_OK;
}

int GraphCmdRange(GRAPH_ARGS)
{
    int range;

    if((argc != 2) && (argc != 3))
    {
        Tcl_AppendResult(interp, "wrong # args", (char *) NULL);
        return TCL_ERROR;
    }

    if(argc == 3)
    {
        if((Tcl_GetInt(interp, argv[2], &range) != TCL_OK) || ((range != 10) && (range != 120)))
        {
            Tcl_AppendResult(interp, " bogus args", (char *) NULL);
            return TCL_ERROR;
        }

        graph->range = range;
        NewGraph = 1;
    }

    sprintf(interp->result, "%d", graph->range);

    return TCL_OK;
}

int GraphCmdMask(GRAPH_ARGS)
{
    int mask;

    if((argc != 2) && (argc != 3))
    {
        Tcl_AppendResult(interp, "wrong # args", (char *) NULL);
        return TCL_ERROR;
    }

    if(argc == 3)
    {
        if((Tcl_GetInt(interp, argv[2], &mask) != TCL_OK) || (mask < 0) || (mask > 63))
        {
            Tcl_AppendResult(interp, " bogus args", (char *) NULL);
            return TCL_ERROR;
        }

        graph->mask = mask;
        NewGraph = 1;
    }

    sprintf(interp->result, "%d", graph->mask);

    return TCL_OK;
}

int DoGraphCmd(CLIENT_ARGS)
{
    SimGraph *graph = (SimGraph *) clientData;
    Tcl_HashEntry *ent;
    int result = TCL_OK;
    int (*cmd) ();

    if(argc < 2)
    {
        return TCL_ERROR;
    }

    if((ent = Tcl_FindHashEntry(&GraphCmds, argv[1])))
    {
        cmd = (int (*)()) ent->clientData;
        Tk_Preserve((ClientData) graph);
        result = cmd(graph, interp, argc, argv);
        Tk_Release((ClientData) graph);
    }
    else
    {
        Tcl_AppendResult(interp, "unknown command name: \"", argv[0], " ", argv[1], "\".", (char *) NULL);
        result = TCL_ERROR;
    }
    return result;
}

int GraphViewCmd(CLIENT_ARGS)
{
    SimGraph *graph;
    Tk_Window tkwin = (Tk_Window) clientData;

    if(argc < 2)
    {
        Tcl_AppendResult(interp, "wrong # args:  should be \"", argv[0], " pathName ?options?\"", (char *) NULL);
        return TCL_ERROR;
    }

    tkwin = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
    if(tkwin == NULL)
    {
        return TCL_ERROR;
    }

    graph = (SimGraph *) ckalloc(sizeof(SimGraph));

    graph->tkwin = tkwin;
    graph->interp = interp;
    graph->flags = 0;

    Tk_SetClass(graph->tkwin, "GraphView");
    Tk_CreateEventHandler(graph->tkwin,
                          VisibilityChangeMask |
                          ExposureMask | StructureNotifyMask, SimGraphEventProc, (ClientData) graph);
    Tcl_CreateCommand(interp, Tk_PathName(graph->tkwin), DoGraphCmd, (ClientData) graph, (void (*)()) NULL);

/*
  Tk_MakeWindowExist(graph->tkwin);
*/

    if(getenv("XSYNCHRONIZE") != NULL)
    {
        XSynchronize(Tk_Display(tkwin), 1);
    }

    InitNewGraph(graph);
    DoNewGraph(graph);

    if(ConfigureSimGraph(interp, graph, argc - 2, argv + 2, 0) != TCL_OK)
    {
        /*
         * XXX: destroy graph 
         */
        Tk_DestroyWindow(graph->tkwin);
        return TCL_ERROR;
    }

    interp->result = Tk_PathName(graph->tkwin);
    return TCL_OK;
}

int ConfigureSimGraph(Tcl_Interp * interp, SimGraph * graph, int argc, char **argv, int flags)
{
    if(Tk_ConfigureWidget(interp, graph->tkwin, GraphConfigSpecs, argc, argv, (char *) graph, flags) != TCL_OK)
    {
        return TCL_ERROR;
    }

    Tk_SetBackgroundFromBorder(graph->tkwin, graph->border);

    EventuallyRedrawGraph(graph);
    return TCL_OK;
}

char *HistName[] = {
    "Residential", "Commercial", "Industrial",
    "Cash Flow", "Crime", "Pollution"
};

unsigned char HistColor[] = {
    COLOR_LIGHTGREEN, COLOR_DARKBLUE, COLOR_YELLOW,
    COLOR_DARKGREEN, COLOR_RED, COLOR_OLIVE
};

void graph_command_init(void)
{
    Tcl_CreateCommand(tk_mainInterp, "graphview", GraphViewCmd, (ClientData) MainWindow, (void (*)()) NULL);

    Tcl_InitHashTable(&GraphCmds, TCL_STRING_KEYS);

#define GRAPH_CMD(name) HASHED_CMD(Graph, name)

    GRAPH_CMD(configure);
    GRAPH_CMD(position);
    GRAPH_CMD(size);
    GRAPH_CMD(Visible);
    GRAPH_CMD(Range);
    GRAPH_CMD(Mask);
}

void drawMonth(short *hist, unsigned char *s, float scale)
{
    register short val;
    register short x;

    for(x = 0; x < 120; x++)
    {
        val = hist[x] * scale;
        if(val < 0)
            val = 0;
        if(val > 255)
            val = 255;
        s[119 - x] = val;
    }
}

void doAllGraphs(void)
{
    float scaleValue;

    AllMax = 0;
    if(ResHisMax > AllMax)
        AllMax = ResHisMax;
    if(ComHisMax > AllMax)
        AllMax = ComHisMax;
    if(IndHisMax > AllMax)
        AllMax = IndHisMax;
    if(AllMax <= 128)
        AllMax = 0;

    if(AllMax)
    {
        scaleValue = 128.0 / AllMax;
    }
    else
    {
        scaleValue = 1.0;
    }

    // scaleValue = 0.5; // XXX

    drawMonth(ResHis, History10[RES_HIST], scaleValue);
    drawMonth(ComHis, History10[COM_HIST], scaleValue);
    drawMonth(IndHis, History10[IND_HIST], scaleValue);
    drawMonth(MoneyHis, History10[MONEY_HIST], 1.0);
    drawMonth(CrimeHis, History10[CRIME_HIST], 1.0);
    drawMonth(PollutionHis, History10[POLLUTION_HIST], 1.0);

    AllMax = 0;
    if(Res2HisMax > AllMax)
        AllMax = Res2HisMax;
    if(Com2HisMax > AllMax)
        AllMax = Com2HisMax;
    if(Ind2HisMax > AllMax)
        AllMax = Ind2HisMax;
    if(AllMax <= 128)
        AllMax = 0;

    if(AllMax)
    {
        scaleValue = 128.0 / AllMax;
    }
    else
    {
        scaleValue = 1.0;
    }

    // scaleValue = 0.5; // XXX

    drawMonth(ResHis + 120, History120[RES_HIST], scaleValue);
    drawMonth(ComHis + 120, History120[COM_HIST], scaleValue);
    drawMonth(IndHis + 120, History120[IND_HIST], scaleValue);
    drawMonth(MoneyHis + 120, History120[MONEY_HIST], 1.0);
    drawMonth(CrimeHis + 120, History120[CRIME_HIST], 1.0);
    drawMonth(PollutionHis + 120, History120[POLLUTION_HIST], 1.0);
}

void ChangeCensus(void)
{
    CensusChanged = 1;
}

void graphDoer(void)
{
    SimGraph *graph;

    if(CensusChanged)
    {
        doAllGraphs();
        NewGraph = 1;
        CensusChanged = 0;
    }

    if(NewGraph)
    {
        for(graph = sim->graph; graph != NULL; graph = graph->next)
        {
            EventuallyRedrawGraph(graph);
        }
        NewGraph = 0;
    }
}

void initGraphs(void)
{
    int i;
    SimGraph *graph;

    for(graph = sim->graph; graph != NULL; graph = graph->next)
    {
        graph->range = 10;
        graph->mask = ALL_HISTORIES;
    }

    if(!HistoryInitialized)
    {
        HistoryInitialized = 1;
        for(i = 0; i < HISTORIES; i++)
        {
            History10[i] = (unsigned char *) ckalloc(120);
            History120[i] = (unsigned char *) ckalloc(120);
        }
    }
}

/* comefrom: InitWillStuff */
void InitGraphMax(void)
{
    register int x;

    ResHisMax = 0;
    ComHisMax = 0;
    IndHisMax = 0;
    for(x = 118; x >= 0; x--)
    {
        if(ResHis[x] > ResHisMax)
            ResHisMax = ResHis[x];
        if(ComHis[x] > ComHisMax)
            ComHisMax = ComHis[x];
        if(IndHis[x] > IndHisMax)
            IndHisMax = IndHis[x];
        if(ResHis[x] < 0)
            ResHis[x] = 0;
        if(ComHis[x] < 0)
            ComHis[x] = 0;
        if(IndHis[x] < 0)
            IndHis[x] = 0;
    }
    Graph10Max = ResHisMax;
    if(ComHisMax > Graph10Max)
        Graph10Max = ComHisMax;
    if(IndHisMax > Graph10Max)
        Graph10Max = IndHisMax;

    Res2HisMax = 0;
    Com2HisMax = 0;
    Ind2HisMax = 0;
    for(x = 238; x >= 120; x--)
    {
        if(ResHis[x] > Res2HisMax)
            Res2HisMax = ResHis[x];
        if(ComHis[x] > Com2HisMax)
            Com2HisMax = ComHis[x];
        if(IndHis[x] > Ind2HisMax)
            Ind2HisMax = IndHis[x];
        if(ResHis[x] < 0)
            ResHis[x] = 0;
        if(ComHis[x] < 0)
            ComHis[x] = 0;
        if(IndHis[x] < 0)
            IndHis[x] = 0;
    }
    Graph120Max = Res2HisMax;
    if(Com2HisMax > Graph120Max)
        Graph120Max = Com2HisMax;
    if(Ind2HisMax > Graph120Max)
        Graph120Max = Ind2HisMax;
}

void InitNewGraph(SimGraph * graph)
{
    graph->next = NULL;
    graph->range = 10;
    graph->mask = ALL_HISTORIES;

/* This stuff was initialized in our caller (GraphCmd) */
/*  graph->tkwin = NULL; */
/*  graph->interp = NULL; */
/*  graph->flags = 0; */

    graph->x = NULL;
    graph->visible = 0;
    graph->w_x = graph->w_y = 0;
    graph->w_width = graph->w_height = 0;
    graph->pixmap = None;
    graph->pixels = NULL;
    graph->fontPtr = NULL;
    graph->border = NULL;
    graph->borderWidth = 0;
    graph->relief = TK_RELIEF_FLAT;
    graph->draw_graph_token = 0;
//fprintf(stderr, "InitNewGraph token %d\n", graph->draw_graph_token);

    graph->x = FindXDisplay(graph->tkwin);
    IncRefDisplay(graph->x);

    graph->pixels = graph->x->pixels;
    graph->fontPtr = NULL;

    DoResizeGraph(graph, 16, 16);
}

void DestroyGraph(SimGraph * graph)
{
    SimGraph **gp;

    for(gp = &sim->graph; (*gp) != NULL; gp = &((*gp)->next))
    {
        if((*gp) == graph)
        {
            (*gp) = graph->next;
            sim->graphs--;
            break;
        }
    }

    if(graph->pixmap != None)
    {
        XFreePixmap(graph->x->dpy, graph->pixmap);
        graph->pixmap = None;
    }

    DecRefDisplay(graph->x);

    ckfree((char *) graph);
}

void DoResizeGraph(SimGraph * graph, int w, int h)
{
    graph->w_width = w;
    graph->w_height = h;

    if(graph->pixmap != None)
    {
        XFreePixmap(graph->x->dpy, graph->pixmap);
        graph->pixmap = None;
    }
    graph->pixmap = XCreatePixmap(graph->x->dpy, graph->x->root, w, h, graph->x->depth);
    if(graph->pixmap == None)
    {
        fprintf(stderr, "Sorry, Micropolis can't create a pixmap on X display \"%s\".\n", graph->x->display);
        sim_exit(1);            // Just sets tkMustExit and ExitReturn
        return;
    }
}

void DoNewGraph(SimGraph * graph)
{
    sim->graphs++;
    graph->next = sim->graph;
    sim->graph = graph;

    NewGraph = 1;
}

#define BORDER 5

void DoUpdateGraph(SimGraph * graph)
{
    Display *dpy;
    GC gc;
    Pixmap pm;
    int *pix;
    unsigned char **hist;
    int w, h, mask, i, j, x, y;
    XPoint points[121];
    int year = (CityTime / 48) + StartingYear;
    int month = (CityTime / 4) % 12;
    int do_top_labels = 0;
    int do_right_labels = 0;
    int top_label_height = 30;
    int right_label_width = 65;
    int tx, ty;
    float sx, sy;

    if(!graph->visible)
    {
        return;
    }

    if(graph->range == 10)
    {
        hist = History10;
    }
    else
    {
        hist = History120;
    }

    dpy = graph->x->dpy;
    gc = graph->x->gc;
    pm = graph->pixmap;
    pix = graph->pixels;

    w = graph->w_width;
    h = graph->w_height;

    XSetFont(graph->x->dpy, graph->x->gc, graph->fontPtr->fid);
    XSetLineAttributes(dpy, gc, 3, LineSolid, CapButt, JoinBevel);
    if(graph->x->color)
    {
        XSetForeground(dpy, gc, pix[COLOR_LIGHTGRAY]);
    }
    else
    {
        XSetForeground(dpy, gc, pix[COLOR_WHITE]);
    }
    XFillRectangle(dpy, pm, gc, 0, 0, w, h);

    tx = BORDER;
    ty = BORDER;

    if((w -= (2 * BORDER)) < 1)
        w = 1;
    if((h -= (2 * BORDER)) < 1)
        h = 1;

    if(w > (4 * right_label_width))
    {
        w -= right_label_width;
        do_right_labels = 1;
    }

    if(do_right_labels && (h > (3 * top_label_height)))
    {
        ty += top_label_height;
        h -= top_label_height;
        do_top_labels = 1;
    }

    sx = ((float) w) / 120.0;
    sy = ((float) h) / 256.0;

    mask = graph->mask;
    for(i = 0; i < HISTORIES; i++, mask >>= 1, hist++)
    {
        if(mask & 1)
        {
            int fg = COLOR_WHITE;
            int bg = COLOR_BLACK;
            Pixmap stipple = None;

            for(j = 0; j < 120; j++)
            {
                x = tx + (j * sx);
                y = ty + ((int) (h - (((float) (*hist)[j]) * sy)));
                points[j].x = x;
                points[j].y = y;
            }
            x = tx + (j * sx);
            points[j].x = x;
            points[j].y = y;

            if(graph->x->color)
            {
                XSetForeground(dpy, gc, pix[HistColor[i]]);
            }
            else
            {
                switch (i)
                {
                    case 0:    /* res */
                        stipple = graph->x->gray50_stipple;
                        break;
                    case 1:    /* com */
                        stipple = graph->x->gray25_stipple;
                        break;
                    case 2:    /* ind */
                        stipple = graph->x->gray75_stipple;
                        break;
                    case 3:    /* cash */
                        fg = COLOR_BLACK;
                        break;
                    case 4:    /* crime */
                        stipple = graph->x->horiz_stipple;
                        break;
                    case 5:    /* pol */
                        stipple = graph->x->vert_stipple;
                        break;
                }
                if(stipple != None)
                {
                    XSetStipple(graph->x->dpy, gc, stipple);
                    XSetTSOrigin(graph->x->dpy, gc, 0, 0);
                    XSetForeground(graph->x->dpy, gc, pix[fg]);
                    XSetBackground(graph->x->dpy, gc, pix[bg]);
                    XSetFillStyle(graph->x->dpy, gc, FillOpaqueStippled);
                }
                else
                {
                    XSetForeground(graph->x->dpy, gc, pix[fg]);
                }
            }

            XDrawLines(dpy, pm, gc, points, 121, CoordModeOrigin);

            if(!graph->x->color && (stipple != None))
            {
                XSetFillStyle(graph->x->dpy, gc, FillSolid);
            }

            if(do_right_labels)
            {
                if(graph->x->color)
                {
                    XSetForeground(dpy, gc, pix[HistColor[i]]);
                    XDrawString(graph->x->dpy, pm, graph->x->gc, x + 4, y + 5, HistName[i], strlen(HistName[i]));
                    XDrawString(graph->x->dpy, pm, graph->x->gc, x + 5, y + 4, HistName[i], strlen(HistName[i]));

                    XSetForeground(dpy, gc, pix[COLOR_BLACK]);
                    XDrawString(graph->x->dpy, pm, graph->x->gc, x + 5, y + 5, HistName[i], strlen(HistName[i]));
                }
                else
                {
                    XSetForeground(dpy, gc, pix[COLOR_BLACK]);
                    XDrawString(graph->x->dpy, pm, graph->x->gc, x + 5, y + 5, HistName[i], strlen(HistName[i]));
                }
            }
        }
    }

    XSetLineAttributes(dpy, gc, 1, LineSolid, CapButt, JoinMiter);

    XSetForeground(dpy, gc, pix[COLOR_BLACK]);
    XDrawLine(dpy, pm, gc, tx, ty - 1, tx + w, ty - 1);
    XDrawLine(dpy, pm, gc, tx, ty + h, tx + w, ty + h);

    if(graph->range == 10)
    {
        for(x = 120 - month; x >= 0; x -= 12)
        {
            int xx, yy;

            xx = tx + (x * sx);
            XDrawLine(dpy, pm, gc, xx, ty - 1, xx, ty + h);
            if(do_top_labels)
            {
                char buf[256];

                sprintf(buf, "%d", year--);
                xx = tx + (x * sx) + 2;
                yy = ty - ((year & 1) ? 4 : 20);
                XDrawString(graph->x->dpy, pm, graph->x->gc, xx, yy, buf, strlen(buf));
            }
        }
    }
    else
    {
        int past;

        sx /= 10;
        past = 10 * (year % 10);
        year /= 10;

        for(x = 1200 - past; x >= 0; x -= 120)
        {
            int xx, yy;

            xx = tx + (x * sx);
            XDrawLine(dpy, pm, gc, xx, ty - 1, xx, ty + h);
            if(do_top_labels)
            {
                char buf[256];

                sprintf(buf, "%d0", year--);

                xx = tx + (x * sx) + 2;
                yy = ty - ((year & 1) ? 4 : 20);
                XDrawString(graph->x->dpy, pm, graph->x->gc, xx, yy, buf, strlen(buf));
            }
        }
    }

    XCopyArea(graph->x->dpy, graph->pixmap,
              Tk_WindowId(graph->tkwin), graph->x->gc, 0, 0, graph->w_width, graph->w_height, 0, 0);
}
